/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "downloadmanager.h"

DownloadManager::DownloadManager(QObject *parent)
    : QObject(parent)
{
    QTimer::singleShot(1000, this, [this](){
        QUrl url = QUrl("https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/jom/jom_0_6_08.zip");
        this->startDownload(url, "D://TestDownload.exe", 4);
    });
}

bool DownloadManager::startDownload(const QUrl url, const QString path, int workNumber)
{
    int fileSize = calcFileSize(url);
    //int fileSize = 19;
    qDebug() << "fileSize: " << fileSize;
    m_output.setFileName(path);
    if (!m_output.open(QIODevice::WriteOnly)) {
        qFatal("downlaod path can not wirte!");
        return false;
    }

    if(fileSize < 0) {
        TaskInfo info;
        info.url = url;
        info.startPos = 0;
        info.endPos = fileSize;
        info.bRange = true;
        m_vecData.push_back(info);
        DownloadTask* task = new DownloadTask(this);
        connect(task, &DownloadTask::readyRead, \
                this, &DownloadManager::downloadReadyRead, Qt::QueuedConnection);
        connect(task, &DownloadTask::finished, \
                this, &DownloadManager::downloadFinished, Qt::QueuedConnection);
        connect(task, &DownloadTask::errorOccurred, \
                this, &DownloadManager::downloadError, Qt::QueuedConnection);
        m_vecTask.push_back(task);
        m_vecTask.back()->start(m_vecData.back());
    } else {
        for(int idx = 0; idx < workNumber; idx++) {
            TaskInfo info;
            info.order = idx;
            info.startPos = fileSize * idx / workNumber;
            info.endPos = fileSize * (idx+1) / workNumber;
            if( idx != 0 ) info.startPos--;
            info.bRange = true;
            info.url = url;
            qDebug() << "start pos: " << info.startPos;
            qDebug() << "end pos: " << info.endPos;
            m_vecData.push_back(info);
            DownloadTask* task = new DownloadTask(this);
            connect(task, &DownloadTask::readyRead, \
                    this, &DownloadManager::downloadReadyRead, Qt::QueuedConnection);
            connect(task, &DownloadTask::finished, \
                    this, &DownloadManager::downloadFinished, Qt::QueuedConnection);
            connect(task, &DownloadTask::errorOccurred, \
                    this, &DownloadManager::downloadError, Qt::QueuedConnection);
            m_vecTask.push_back(task);
            m_vecTask.back()->start(m_vecData.back());
        }
    }

    m_downloadTimer.start();
    return true;
}

int DownloadManager::calcFileSize(const QUrl &url)
{
    int fileSize = -1;
    QNetworkAccessManager manager;
    QNetworkRequest request;
    request.setUrl(url);
    QNetworkReply* headReply = manager.head(request);
    QEventLoop loop;
    QTimer::singleShot(5000, &loop, SLOT(quit()));
    connect(headReply, &QNetworkReply::finished, [&loop, &headReply, &fileSize](){
        if (headReply->hasRawHeader(QString("Content-Length").toUtf8())) {
            QString size = headReply->rawHeader(QString("Content-Length").toUtf8());
            qDebug() << "size: " << size;
            fileSize = size.toInt();
            qDebug() << "fileSize: " << size;
        }
        loop.quit();
    });

    loop.exec();
    return fileSize;
}


void DownloadManager::downloadReadyRead(int order)
{
    qDebug() << "order: " << order;
//    if (!m_output.seek(m_vecData[order].startPos + m_vecData[order].haveBytes))
//        return;
    QByteArray byteData = m_vecTask[order]->reply()->readAll();
    m_output.seek(m_vecData[order].startPos + m_vecData[order].haveBytes);
    m_output.write(byteData);
    m_vecData[order].haveBytes += byteData.size();
    //    // calculate the download speed
    //double speed = bytesReceived * 1000.0 / downloadTimer.elapsed();
    //    QString unit;
    //    if (speed < 1024) {
    //        unit = "bytes/sec";
    //    } else if (speed < 1024*1024) {
    //        speed /= 1024;
    //        unit = "kB/s";
    //    } else {
    //        speed /= 1024*1024;
    //        unit = "MB/s";
    //    }
}

void DownloadManager::downloadFinished(int order)
{
    m_vecData[order].bFinish = true;
    auto result = std::all_of(m_vecData.begin(), m_vecData.end(), [](TaskInfo info){
        return (info.bFinish == true);
    });

    if(result) {
        m_output.close();
    }
}

void DownloadManager::downloadError(int order)
{
    qDebug() << m_vecTask[order]->reply()->error();
}


//QString DownloadManager::saveFileName(const QUrl &url)
//{
//    QString path = url.path();
//    QString basename = QFileInfo(path).fileName();

//    if (basename.isEmpty())
//        basename = "download";

//    if (QFile::exists(basename)) {
//        // already exists, don't overwrite
//        int i = 0;
//        basename += '.';
//        while (QFile::exists(basename + QString::number(i)))
//            ++i;

//        basename += QString::number(i);
//    }

//    return basename;
//}

//void DownloadManager::startNextDownload()
//{
//    if (downloadQueue.isEmpty()) {
//        printf("%d/%d files downloaded successfully\n", downloadedCount, totalCount);
//        emit finished();
//        return;
//    }

//    QUrl url = downloadQueue.dequeue();

//    QString filename = saveFileName(url);
//    output.setFileName(filename);
//    if (!output.open(QIODevice::WriteOnly)) {
//        fprintf(stderr, "Problem opening save file '%s' for download '%s': %s\n",
//                qPrintable(filename), url.toEncoded().constData(),
//                qPrintable(output.errorString()));

//        startNextDownload();
//        return;                 // skip this download
//    }

//    QNetworkRequest request(url);
//    currentDownload = manager.get(request);
//    connect(currentDownload, &QNetworkReply::downloadProgress,
//            this, &DownloadManager::downloadProgress);
//    connect(currentDownload, &QNetworkReply::finished,
//            this, &DownloadManager::downloadFinished);
//    connect(currentDownload, &QNetworkReply::readyRead,
//            this, &DownloadManager::downloadReadyRead);

//    // prepare the output
//    printf("Downloading %s...\n", url.toEncoded().constData());
//    downloadTimer.start();
//}

//void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
//{
//    progressBar.setStatus(bytesReceived, bytesTotal);

//    // calculate the download speed
//    double speed = bytesReceived * 1000.0 / downloadTimer.elapsed();
//    QString unit;
//    if (speed < 1024) {
//        unit = "bytes/sec";
//    } else if (speed < 1024*1024) {
//        speed /= 1024;
//        unit = "kB/s";
//    } else {
//        speed /= 1024*1024;
//        unit = "MB/s";
//    }

//    progressBar.setMessage(QString::fromLatin1("%1 %2")
//                           .arg(speed, 3, 'f', 1).arg(unit));
//    progressBar.update();
//}

//void DownloadManager::downloadFinished()
//{
//    progressBar.clear();
//    output.close();

//    if (currentDownload->error()) {
//        // download failed
//        fprintf(stderr, "Failed: %s\n", qPrintable(currentDownload->errorString()));
//        output.remove();
//    } else {
//        // let's check if it was actually a redirect
//        if (isHttpRedirect()) {
//            reportRedirect();
//            output.remove();
//        } else {
//            printf("Succeeded.\n");
//            ++downloadedCount;
//        }
//    }

//    currentDownload->deleteLater();
//    startNextDownload();
//}

//void DownloadManager::downloadReadyRead()
//{
//    output.write(currentDownload->readAll());
//}

//bool DownloadManager::isHttpRedirect() const
//{
//    int statusCode = currentDownload->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//    return statusCode == 301 || statusCode == 302 || statusCode == 303
//           || statusCode == 305 || statusCode == 307 || statusCode == 308;
//}

//void DownloadManager::reportRedirect()
//{
//    int statusCode = currentDownload->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//    QUrl requestUrl = currentDownload->request().url();
//    QTextStream(stderr) << "Request: " << requestUrl.toDisplayString()
//                        << " was redirected with code: " << statusCode
//                        << '\n';

//    QVariant target = currentDownload->attribute(QNetworkRequest::RedirectionTargetAttribute);
//    if (!target.isValid())
//        return;
//    QUrl redirectUrl = target.toUrl();
//    if (redirectUrl.isRelative())
//        redirectUrl = requestUrl.resolved(redirectUrl);
//    QTextStream(stderr) << "Redirected to: " << redirectUrl.toDisplayString()
//                        << '\n';
//}
